package com.codedef.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.codedef.common.exception.CustomException;
import com.codedef.common.response.ResultCode;
import com.codedef.entity.JwtParam;
import com.codedef.entity.MqSeatBo;
import com.codedef.entity.ReservationRecord;
import com.codedef.entity.ReservationRecordVo;
import com.codedef.fservice.SeatService;
import com.codedef.fservice.UserService;
import com.codedef.mapper.ReservationRecordMapper;
import com.codedef.util.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLocalCachedMap;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;


/**
 * @author 孙宏宇
 * @date 2021/2/1 16:46
 */
@Slf4j
@Service
public class ReservationRecordService {

    @Autowired
    ReservationRecordMapper reservationRecordMapper;

    @Autowired
    JwtParam jwtParam;

    @Autowired
    RedissonClient redissonClient;

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Autowired
    BloomCacheManager bloomCacheManager;

    @Autowired
    SeatService seatService;

    @Autowired
    UserService userService;


    public boolean seatReserve(ReservationRecord reservationRecord, String uId) {
        /*  数据合法性检查  */
        // 结束时间大于开始时间
        if (reservationRecord.getEndTime().getTime() <= reservationRecord.getStartTime().getTime()) {
            throw new CustomException(ResultCode.ENDTIME_MUST_GREATER_STARTTIME);
        }

        String abbreviation = seatService.getClassroomAbbreviationBySeatId(reservationRecord.getSeatId());

        // 使用 Redisson 分布式锁
        RLock seatLock = redissonClient.getLock("seatLock_" + reservationRecord.getSeatId());
        RLock userLock = redissonClient.getLock("userLock_" + reservationRecord.getUserId());

        try {
            seatLock.lock();
            /*  1. 该座位是否是空闲状态  */
            RLocalCachedMap<Integer, Integer> seatStateCache = bloomCacheManager.getCachedMap(abbreviation);
            int state = seatStateCache.get(reservationRecord.getSeatId());
            if (state == 1)  // 已被预约抛出异常
                throw new CustomException(ResultCode.SEAT_HAS_BEEN_RESERVED);

            userLock.lock();
            /*  2. 该用户这个时间段是否已存在预约  */
            int cnt = reservationRecordMapper.selectUserReservationCountByTimePeriod(reservationRecord.getStartTime(), reservationRecord.getEndTime());
            if (cnt > 0)
                throw new CustomException(ResultCode.YOU_HAVE_OTHER_RESERVATION);

            /*  3. 预约该座位  */
            // 3.1 写入一条ReservationRecord到数据库
            reservationRecord.setUserId(Integer.parseInt(uId));
            reservationRecord.setDate(reservationRecord.getStartTime());
            reservationRecord.setReservationState(1);
            reservationRecord.setEntryTime(new Date());
            reservationRecordMapper.insert(reservationRecord);

        } finally {
            userLock.unlock();
            seatLock.unlock();
        }

        /*  3.2 向RabbitMQ发送一条延时消息  */
        // 构建BO
        MqSeatBo mqSeatBo = new MqSeatBo(reservationRecord.getId(), reservationRecord.getSeatId(), Integer.parseInt(uId), abbreviation);
        // 发延时消息
        rabbitTemplate.convertAndSend("codedef.delayed", "reservation_que", mqSeatBo, message -> {
            message.getMessageProperties().setDelay(
                    (int) DateUtil.subtractTwoDate(reservationRecord.getStartTime(), new Date()) // + 15 * 60 * 1000
            );
            return message;
        });

        // 增加以此预约完成次数 UserID
        userService.increaseReservationNumberFeign(mqSeatBo.getUserId());

        log.info("Id为 " + uId + " 的用户成功预约了一个座位 " + reservationRecord);
        return true;
    }

    /**
     * 超过一个预约可签到的最晚时间后，检查用户是否签到
     *
     * @param mqSeatBo
     */
    @RabbitListener(queues = "reservation_que")
    public void checkUserSignIn(MqSeatBo mqSeatBo) {
        log.info("checkUserSignIn：" + mqSeatBo);
        // 1.查数据库这条预约的状态
        ReservationRecord record = reservationRecordMapper.selectOne(new QueryWrapper<ReservationRecord>().eq("id", mqSeatBo.getReservationRecordId()));
        if (record == null) return;
        // 判断预约的状态
        if (record.getReservationState() == 1) {
            /* 座位处于被预约状态， 但仍然没有签到，违约处理 */
            // 修改该ReservationRecord的预约状态
            reservationRecordMapper.updateReservationStateById(mqSeatBo.getReservationRecordId(), -10);
            // 该用户的违约次数+1
            // TODO 查询的时候 不显示状态为 -1 -10的记录
            userService.increaseDefaultNumber(mqSeatBo.getUserId());

            log.error("Id为 " + mqSeatBo.getUserId() + " 的用户违约了 \n" + mqSeatBo + '\n');

        } else if (record.getReservationState() == 10){
            // 用户已签到   发送一个延时消息
            // RabbitMQ 创建延时消息
            this.rabbitTemplate.convertAndSend("codedef.delayed", "use_que", mqSeatBo, message -> {
                message.getMessageProperties().setDelay(
                        (int) DateUtil.subtractTwoDate(record.getEndTime(), new Date())
                );
                return message;
            });
            log.error("Id为 " + mqSeatBo.getUserId() + " 的用户已签到 \n" + mqSeatBo + '\n');
        }

    }


//    /* 预约一个座位  (后期使用事务) 预约失败抛出不同的异常 由全局异常Handler转发
//     * @param reservationRecord 预约记录实体类
//     * @param uId               用户的id
//     * @return 是否预约成功
//     */
//    private boolean seatReserveBack(ReservationRecord reservationRecord, String uId) {
//        // 该方法需要特殊处理 保证原子性
//        // 判断：该座位是否是空闲状态、该用户在该时段是否已有预约、
//
//        // 从Token的载荷获取UserId  迁移完成
//        Integer userId = Integer.parseInt(uId);
//        reservationRecord.setUserId(userId);
//
//        // 计算结束时间与当前时间差 的毫秒数
//        long nows = new Date().getTime();
//        int ms = (int) (reservationRecord.getEndTime().getTime() - nows);
//        log.error("reservationRecord.getEndTime().getTime(): " + reservationRecord.getEndTime().getTime());
//        log.error("nows: " + nows + "  " + nows);
//        log.error("计算出的时间差为: " + ms);
//
//        // 将预约记录放入数据库
//        int insertResult = reservationRecordMapper.insert(reservationRecord);
//
//        // 查询数据库 获得座位所属的Classroom
//        // 不再使用abbreviation 使用教室的id座位CacheMap的名字
//        String abbreviation = seatService.getClassroomAbbreviationBySeatId(reservationRecord.getSeatId());
//        // 构建BO
//        MqSeatBo mqSeatBo = new MqSeatBo(reservationRecord.getId(), reservationRecord.getSeatId(), userId, abbreviation);
//        // RabbitMQ 创建延时消息
//        this.rabbitTemplate.convertAndSend("codedef.delayed", "use_que", mqSeatBo, message -> {
//            message.getMessageProperties().setDelay(ms);
//            return message;
//        });
//        // 放入Redis中的布隆过滤器
//        RLocalCachedMap<Integer, Integer> seatStateCache = bloomCacheManager.getCachedMap(abbreviation);
//        seatStateCache.fastPut(reservationRecord.getSeatId(), 1);
//
//        return insertResult > 0;
//    }

    /**
     * 预约时间到, 会回调这个方法
     * 监听RabbitMQ的延时队列
     */
    @RabbitListener(queues = "use_que")
    public void seatReservationTimeLimit(MqSeatBo mqSeatBo) {
//        log.error("seatReservationTimeLimit 监听到Rabbit的回调: \n" + mqSeatBo);

        // 修改布隆过滤器的状态
        RLocalCachedMap<Integer, Integer> cachedMap = bloomCacheManager.getCachedMap(mqSeatBo.getClassroomAbbreviation());
        cachedMap.fastPut(mqSeatBo.getSeatId(), 0);

        // 修改数据库中 预约记录的状态 ReserveID
        reservationRecordMapper.updateReservationStateById(mqSeatBo.getReservationRecordId(), 20);

        // 增加以此预约完成次数 UserID
//        userService.increaseReservationNumberFeign(mqSeatBo.getUserId());

        log.error("Id为 " + mqSeatBo.getUserId() + " 的用户预约时间已到 \n" + mqSeatBo + '\n');
    }

    /**
     * 更新一个预约记录的状态
     */
    public boolean updateReservationStateById(Integer reservationRecordId, Integer state) {
        log.info("更新一个预约记录的状态 reservationRecordId:" + reservationRecordId + "  state:" + state);
        return reservationRecordMapper.updateReservationStateById(reservationRecordId, state) > 0;
    }

    /**
     * 查询 某个人 某个座位 当前可以签到的预约 (15分)
     */
    public ReservationRecord getCanSignInReservationRecord(Integer userId, Integer seatId) {
        // 迁移完成

        return reservationRecordMapper.selectCanSignInReservationRecord(userId, seatId);
    }

    /**
     * 查询我的预约列表
     */
    public List<ReservationRecordVo> getReservationListByUserId(Integer userId) {
//        return reservationRecordMapper.selectList(new QueryWrapper<ReservationRecord>().eq("user_id", userId));
        return reservationRecordMapper.selectReservationListByUserId(userId);
    }


    /**
     * 获取一个座位某天的预约列表
     */
    public List<ReservationRecord> getReservationListByDateAndSeatId(String date, Integer seatId) {
        return reservationRecordMapper.selectReservationListByDateAndSeatId(date, seatId);
    }

    /**
     * 取消一个预约
     */
    public int cancelReserve(Integer reservationId, Integer userId) {
        // 查出当前取消预约的记录是否属于 当前登录的用户
        ReservationRecord dbRecord = reservationRecordMapper.selectOne(new QueryWrapper<ReservationRecord>().eq("id", reservationId));
        if (!dbRecord.getUserId().equals(userId)) {
            throw new CustomException(ResultCode.PERMISSION_UNAUTHORISE);
        }

        // 通过实体类注入要修改的信息
        ReservationRecord record = new ReservationRecord();
        record.setId(reservationId);
        record.setReservationState(-1);
        // 调用Dao层执行修改
        return reservationRecordMapper.updateById(record);
    }

}
